package org.realteam.hibernate.test.model.inheritance;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import org.apache.commons.lang.reflect.MethodUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.hibernate.HibernateException;
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.UserType;

public class HibernateEnumType implements UserType, ParameterizedType, Serializable{

	/**
	 * 
	 */
	private static final long serialVersionUID = -2112725360359886997L;
	
	private static final Logger log = LogManager.getLogger(HibernateEnumType.class);
	
	private static final String GET_METHOD_NAME = "getValue";
	
	private static final String ENUM_CLASS = "enumClass";

	private Class enumClazz;
	
	public Object assemble(Serializable cached, Object value) throws HibernateException {
		return cached;
	}

	public Object deepCopy(Object value) throws HibernateException {
		return value;
	}

	public Serializable disassemble(Object value) throws HibernateException {
		return (Serializable)value;
	}

	public boolean equals(Object x, Object y) throws HibernateException {
		if (x == y)
			return true;
		
		if (x == null || y == null)
			return false;
		
		return x.equals(y);
	}

	public int hashCode(Object value) throws HibernateException {
		return value.hashCode();
	}

	public boolean isMutable() {
		return false;
	}

	public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
		Integer result = rs.getInt(names[0]);
		
		if (result == null)
			return null;
		else {
			List<Enum> enumList = new ArrayList<Enum>(EnumSet.allOf(enumClazz));
			Object returnObj = null;
			
			for (Iterator iter = enumList.iterator(); iter.hasNext();) {
				Enum enumObj = (Enum) iter.next();
				try {
					Object value = MethodUtils.invokeMethod(enumObj, GET_METHOD_NAME, null);
					if(value.equals(result)) {
						returnObj = enumObj;
						break;
					}
				} catch (NoSuchMethodException e1) {
					e1.printStackTrace();
				} catch (IllegalAccessException e1) {
					e1.printStackTrace();
				} catch (InvocationTargetException e1) {
					e1.printStackTrace();
				}
			}
			
			return returnObj;
		}
	}

	public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
		
		if (value == null) 
			st.setNull(index, Types.INTEGER);
		else {
			boolean methodContainsFlag = false;
			
			Method[] methods = enumClazz.getMethods();
			for (Method method : methods) {
				if (method.getName().equals(GET_METHOD_NAME)) {
					methodContainsFlag = true;
					break;
				}
			}
			
			if (methodContainsFlag) {
				Method method = null;
				Object enumValue = null;
				try {
					method = enumClazz.getMethod(GET_METHOD_NAME, null);
					enumValue = method.invoke(value, null);
				} catch (SecurityException e) {
					e.printStackTrace();
				} catch (NoSuchMethodException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				}
				
				st.setInt(index, Integer.valueOf(enumValue.toString()));
			} else 
				throw new HibernateException("the hibernate enumClass ['" + enumClazz + "'] has not such method ['" + GET_METHOD_NAME + "']");
		}
	}

	public Object replace(Object original, Object target, Object owner) throws HibernateException {
		return original;
	}

	public Class returnedClass() {
		return enumClazz;
	}

	public int[] sqlTypes() {
		return new int[]{Types.INTEGER};
	}

	public void setParameterValues(Properties parameters) {
		
		try {
			String enumClassName = (String) parameters.get(ENUM_CLASS);
			ClassLoader clazzLoader = Thread.currentThread().getContextClassLoader();
			enumClazz = Class.forName(enumClassName, true, clazzLoader);
		} catch (ClassNotFoundException e) {
			log.error("EnumClass can not be found!", e);
			throw new HibernateException("EnumClass can not be found!", e);
		}
	}
	
}
































